package edu.hawaii.myisern.commandline;

import java.io.File;
import java.io.StringWriter;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import edu.hawaii.myisern.collaborations.jaxb.Collaboration;
import edu.hawaii.myisern.collaborations.jaxb.Collaborations;
import edu.hawaii.myisern.organizations.jaxb.Organization;
import edu.hawaii.myisern.organizations.jaxb.Organizations;
import edu.hawaii.myisern.researchers.jaxb.Researchers;
import edu.hawaii.myisern.researchers.jaxb.Researcher;

/**
 * Provides code for loading XML and marshalling it into their JAXB related classes.
 * 
 * @author Philip Johnson
 * @author Phuoc Le
 * @author Ka Yee Leung
 * @author Jared Sunouchi
 */
public class MyIsernXmlLoader {

  /** Holds the class-wide JAXBContext, which is thread-safe. */
  private JAXBContext collaborationsJaxbContext;
  /** Holds the class-wide JAXBContext, which is thread-safe. */
  private JAXBContext organizationsJaxbContext;
  /** Holds the class-wide JAXBContext, which is thread-safe. */
  private JAXBContext researchersJaxbContext;
  /** Holds the class-wide Collaborations, which is thread-safe. */
  private Collaborations collaborations;
  /** Holds the class-wide Organizations, which is thread-safe. */
  private Organizations organizations;
  /** Holds the class-wide Researchers, which is thread-safe. */
  private Researchers researchers;
  private String collaborationsFilePath = System.getProperty("user.dir")
      + "/xml/examples/collaborations.example.xml";
  private String organizationsFilePath = System.getProperty("user.dir")
      + "/xml/examples/organizations.example.xml";
  private String researchersFilePath = System.getProperty("user.dir")
      + "/xml/examples/researchers.example.xml";
  
  /** Should this be static? */
  private static final String newLineWithSpaces = "\n   ";

  /** Create and hold the unique ID for researchers. */
  private Map<String, String> uniqueIdsAndUrls = new HashMap<String, String>();
  /** Holds Sanity Check result warning message. */
  private List<String> sanityCheckResults = new ArrayList<String>();

  /**
   * Initializes this instance by reading in the three example XML files.
   * 
   * @throws Exception If problems occur.
   */
  public MyIsernXmlLoader() throws Exception {
    // Load in the Collaborations XML example file.
    this.collaborationsJaxbContext = JAXBContext
        .newInstance("edu.hawaii.myisern.collaborations.jaxb");
    File collaborationsFile = new File(this.collaborationsFilePath);
    Unmarshaller unmarshaller = this.collaborationsJaxbContext.createUnmarshaller();
    this.collaborations = (Collaborations) unmarshaller.unmarshal(collaborationsFile);

    // Do the same for organizations.
    this.organizationsJaxbContext = JAXBContext
        .newInstance("edu.hawaii.myisern.organizations.jaxb");
    File organizationsFile = new File(this.organizationsFilePath);
    unmarshaller = this.organizationsJaxbContext.createUnmarshaller();
    this.organizations = (Organizations) unmarshaller.unmarshal(organizationsFile);

    // Now do it once more for the researchers.
    this.researchersJaxbContext = JAXBContext.newInstance("edu.hawaii.myisern.researchers.jaxb");
    File researchersFile = new File(this.researchersFilePath);
    unmarshaller = this.researchersJaxbContext.createUnmarshaller();
    this.researchers = (Researchers) unmarshaller.unmarshal(researchersFile);

    uniqueIdsAndUrls.clear();
  }

  /**
   * Initializes this instance by using the three XML file parameters.
   * 
   * @param collaborationsFile The collaboration XML file.
   * @param organizationsFile The organization XML file.
   * @param researchersFile The researcher XML file.
   * @throws Exception If problems occur.
   */
  public MyIsernXmlLoader(File collaborationsFile, File organizationsFile, File researchersFile)
      throws Exception {
    // Load in the Collaborations XML example file.
    this.collaborationsJaxbContext = JAXBContext
        .newInstance("edu.hawaii.myisern.collaborations.jaxb");
    Unmarshaller unmarshaller = this.collaborationsJaxbContext.createUnmarshaller();
    this.collaborations = (Collaborations) unmarshaller.unmarshal(collaborationsFile);
    this.collaborationsFilePath = collaborationsFile.getAbsolutePath();

    // Do the same for organizations.
    this.organizationsJaxbContext = JAXBContext
        .newInstance("edu.hawaii.myisern.organizations.jaxb");
    unmarshaller = this.organizationsJaxbContext.createUnmarshaller();
    this.organizations = (Organizations) unmarshaller.unmarshal(organizationsFile);
    this.organizationsFilePath = organizationsFile.getAbsolutePath();

    // Now do it once more for the researchers.
    this.researchersJaxbContext = JAXBContext.newInstance("edu.hawaii.myisern.researchers.jaxb");
    unmarshaller = this.researchersJaxbContext.createUnmarshaller();
    this.researchers = (Researchers) unmarshaller.unmarshal(researchersFile);
    this.researchersFilePath = researchersFile.getAbsolutePath();

    uniqueIdsAndUrls.clear();
  }

  /**
   * Returns the Collaborations.
   * 
   * @return The collaborations.
   */
  public Collaborations getCollaborations() {
    return this.collaborations;
  }

  /**
   * Returns the number of Collaboration instances.
   * 
   * @return The number of collaborations.
   */
  public int getNumCollaborations() {
    return this.collaborations.getCollaboration().size();
  }

  /**
   * Returns the Organizations.
   * 
   * @return The organizations.
   */
  public Organizations getOrganizations() {
    return this.organizations;
  }

  /**
   * Returns the number of Organization instances.
   * 
   * @return The number of organizations.
   */
  public int getNumOrganizations() {
    return this.organizations.getOrganization().size();
  }

  /**
   * Returns the Researchers.
   * 
   * @return The researchers.
   */
  public Researchers getResearchers() {
    return this.researchers;
  }

  /**
   * Returns the number of Researcher instances.
   * 
   * @return The number of researchers.
   */
  public int getNumResearchers() {
    return this.researchers.getResearcher().size();
  }

  /**
   * Returns the file path of the collaborations XML file.
   * 
   * @return The collaborations XML file path.
   */
  public String getCollaborationsFilePath() {
    return this.collaborationsFilePath;
  }

  /**
   * Returns the file path of the organization XML file.
   * 
   * @return The organization XML file path.
   */
  public String getOrganizationsFilePath() {
    return this.organizationsFilePath;
  }

  /**
   * Returns the file path of the researcher XML file.
   * 
   * @return The researcher XML file path.
   */
  public String getRsearchersFilePath() {
    return this.researchersFilePath;
  }
  
  /**
   * Checks Researchers, Organizations, and Collaborations to see if their reference is valid and
   * exists in database.
   * 
   * @return true if no sanity error occur, false if 1 or more sanity error
   */
  public boolean sanityCheck() {
    // Clears the list of warnings from sanity check.
    clearSanityCheckResults();
    boolean sanityCheck = true;
    String warningLabel = "[Warning] ";
    String rLabel = "Researcher ";
    String oLabel = "Organization ";
    String cLabel = "Collaboration ";
    String doesNotExist = " does not exist in the database.";
    String fieldIsBlank = " field is blank ";
    String forLabel = "for ";

    List<Researcher> researchersList = researchers.getResearcher();
    List<Organization> organizationsList = organizations.getOrganization();
    List<Collaboration> collaborationsList = collaborations.getCollaboration();

    // Checks for duplicate names.
    for (Researcher researcher : researchersList) {
      if (createUniqueId(researcher.getName()) == null) {
        sanityCheck = false;
      }
    }
    for (Organization organization : organizationsList) {
      if (createUniqueId(organization.getName()) == null) {
        sanityCheck = false;
      }
    }
    for (Collaboration collaboration : collaborationsList) {
      if (createUniqueId(collaboration.getName()) == null) {
        sanityCheck = false;
      }
    }

    // Researcher check
    // Checks if Organization exist in the database
    // also takes note if no Organization is inputed
    for (Researcher researcher : researchersList) {
      // Checks to see if Researcher name is blank.
      if (researcher.getName().isEmpty()) {
        sanityCheck = false;
        String temp = warningLabel + rLabel;
        this.sanityCheckResults.add(temp + researcher.getName() + fieldIsBlank);
      }
      // Checks to see if there is any Organizations in this Researcher
      String org = researcher.getOrganization();
      if (org.trim().isEmpty()) {
        String temp = warningLabel + oLabel + fieldIsBlank;
        this.sanityCheckResults.add(temp + forLabel + researcher.getName());
      }
      else {
        // Checks to see if the Organization of this Researcher is in the database.
        if (createUniqueId(org.replace(' ', '_')) != null) {
          sanityCheck = false;
          this.sanityCheckResults.add(warningLabel + oLabel + org + doesNotExist);
        }
      }
    }
    // Organization check
    // Checks if Contact and Affiliated Researchers exist in the database
    // also takes note if no Contact of Affiliated Researchers is inputed
    for (Organization organization : organizationsList) {
      // Checks to see if Organization name is blank.
      if (organization.getName().isEmpty()) {
        sanityCheck = false;
        String temp = warningLabel + oLabel;
        this.sanityCheckResults.add(temp + organization.getName() + fieldIsBlank);
      }

      String contact = organization.getContact();
      if (contact.trim().isEmpty()) {
        String temp = warningLabel + "Contact" + fieldIsBlank;
        this.sanityCheckResults.add(temp + forLabel + organization.getName());
      }
      else {
        // Checks Contact
        if (createUniqueId(contact.replace(' ', '_')) != null) {
          sanityCheck = false;
          this.sanityCheckResults.add(warningLabel + rLabel + contact + doesNotExist);
        }
      }
      List<String> aRList = organization.getAffiliatedResearchers().getAffiliatedResearcher();
      if (aRList.isEmpty()) {
        String temp = warningLabel + "Affiliated Researchers" + fieldIsBlank;
        this.sanityCheckResults.add(temp + forLabel + organization.getName());
      }
      else {
        // Checks every Affiliated Researcher
        for (String affResearcher : aRList) {
          if (createUniqueId(affResearcher.replace(' ', '_')) != null) {
            sanityCheck = false;
            this.sanityCheckResults.add(warningLabel + rLabel + affResearcher + doesNotExist);
          }
        }
      }
    }
    // Collaboration check
    // Checks if Collaborating Organizations exist in the database
    // also takes note if no Collaborating Organization is inputed
    for (Collaboration collaboration : collaborationsList) {
      // Checks to see if Collaboration name is blank.
      if (collaboration.getName().isEmpty()) {
        sanityCheck = false;
        String temp = warningLabel + cLabel;
        this.sanityCheckResults.add(temp + collaboration.getName() + fieldIsBlank);
      }

      List<String> cOList = collaboration.getCollaboratingOrganizations()
          .getCollaboratingOrganization();
      if (cOList.isEmpty()) {
        String temp2 = warningLabel + "Collaborating Organizations" + fieldIsBlank;
        this.sanityCheckResults.add(temp2 + forLabel + collaboration.getName());
      }
      else {
        // Checks every Collaborating Organization
        for (String collOrganization : cOList) {
          if (createUniqueId(collOrganization.replace(' ', '_')) != null) {
            sanityCheck = false;
            this.sanityCheckResults.add(warningLabel + oLabel + collOrganization + doesNotExist);
          }
        }
      }
      // Checks every Year
      List<BigInteger> yearsList = collaboration.getYears().getYear();
      if (yearsList.isEmpty()) {
        this.sanityCheckResults.add(warningLabel + cLabel + "Years is empty.");
      }
      else {
        for (BigInteger year : yearsList) {
          if (!(year.intValue() >= 1990 && year.intValue() <= 2010) 
              && !(year.intValue() == 1900)) {
            sanityCheck = false;
            this.sanityCheckResults.add(warningLabel + collaboration.getName() + " " + cLabel 
                + "Years is not between 1990 and 2010");
          }
        }
      }
    }
    return sanityCheck;
  }

  /**
   * Returns the sanity check list.
   * 
   * @return the sanity check list.
   */
  public List<String> getSanityCheckResults() {
    return this.sanityCheckResults;
  }

  /**
   * Clears the Sanity Check List.
   * 
   */
  public void clearSanityCheckResults() {
    this.sanityCheckResults.clear();
  }

  /**
   * Returns the unique IDs of each collaboration, organization, and researcher.
   * 
   * @return The unique IDs.
   */
  public Map<String, String> getUniqueIds() {
    return this.uniqueIdsAndUrls;
  }

  /**
   * Creates a unique ID based on the name of a researcher, organization, or collaboration.
   * 
   * @param name The name of the researcher, organization, or collaboration.
   * @return The unique ID or null if it is not unique.
   */
  public String createUniqueId(String name) {
    String id = name.replace(' ', '_');
    if (uniqueIdsAndUrls.containsKey(id)) {
      return null;
    }
    uniqueIdsAndUrls.put(id, null);
    return id;
  }

  /**
   * THE METHOD BELOW IS THE OLD SANITY CHECK. KEEPING IT FOR BACKUP REASONS.
   */
  /**
   * Generates unique IDs and checks XMLs for all researchers, collaborations, and organizations.
   * 
   * @return Whether the XML data checks out and the unique ID was generated successfully.
   */
  public boolean generateUniqueIdsAndUrls() {
    List<Researcher> researcherList = researchers.getResearcher();
    List<Collaboration> collaborationsList = collaborations.getCollaboration();
    List<Organization> organizationsList = organizations.getOrganization();

    // generates unique IDs for all researchers
    // validates XML data
    for (Researcher researcher : researcherList) {
      if (researcher.getName().isEmpty()) {
        System.out.println("Please enter a researcher name.");
        return false;
      }
      String id = createUniqueId(researcher.getName());
      if (id == null) {
        System.out.println("Name " + researcher.getName() + " is not unique.");
        return false;
      }
      if (!(checkValidLink(researcher.getPictureLink(), id))) {
        System.out.println("Picture link: " + researcher.getPictureLink() + " for researcher "
            + researcher.getName() + ", is not valid.");
        return false;
      }
    }

    // generates unique IDs for all collaborations
    // validates XML data
    for (Collaboration collaboration : collaborationsList) {
      try {
        List<BigInteger> yearsList = collaboration.getYears().getYear();
        for (BigInteger year : yearsList) {
          if (!(year.intValue() == 1900) && ((year.intValue() < 1990) 
              || (year.intValue() > 2010))) {
            System.out.println("Year is: " + year.intValue());
            System.out.println("Invalid year for collaboration " + collaboration.getName()
                + " Year must be between 1990 and 2010.");
            return false;
          }
        }
      }
      catch (Exception e) {
        // catch empty year list
        System.out.println("Empty Years.");
      }
      if (collaboration.getName().isEmpty()) {
        System.out.println("Please enter a collaboration name.");
        return false;
      }
      String id = createUniqueId(collaboration.getName());
      if (id == null) {
        System.out.println("Name " + collaboration.getName() + " is not unique.");
        return false;
      }
    }

    // generates unique IDs for all organizations
    // validates XML data
    for (Organization organization : organizationsList) {
      if (organization.getName().isEmpty()) {
        System.out.println("Please enter an organization name.");
        return false;
      }
      String id = createUniqueId(organization.getName());
      if (id == null) {
        System.out.println("Name " + organization.getName() + " is not unique.");
        return false;
      }
      if (!(checkValidLink(organization.getHomePage(), id))) {
        System.out.println("Homepage link is not valid for organization " + organization.getName()
            + ".");
        return false;
      }
    }
    return true;
  }

  /**
   * Checks if the given URL string is a valid web page.
   * 
   * @param url The string representation of the URL.
   * @param id The unique ID string.
   * @return The boolean value of whether the link is valid or not.
   */
  public boolean checkValidLink(String url, String id) {
    /*
     * try { WebConversation wc = new WebConversation(); wc.getResponse(url); } catch (Exception
     * error) { return false; }
     */

    // map the URL to the ID
    uniqueIdsAndUrls.put(id, url);

    return true;
  }

  /**
   * Displays the location of the XML files that are read in for data.
   * 
   * @return The string of file names and their directory.
   */
  public String getXmlFilesName() {
    String files = "Reading data in from the following XML files:" + newLineWithSpaces
        + collaborationsFilePath + newLineWithSpaces + organizationsFilePath + newLineWithSpaces
        + researchersFilePath;

    return files;
  }

  /**
   * Returns the current Collaborations instance as a String encoding of its XML representation.
   * 
   * @return Its XML String representation.
   * @throws Exception If problems occur during translation.
   */
  public String getCollaborationsXml() throws Exception {
    Marshaller marshaller = this.collaborationsJaxbContext.createMarshaller();
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
    Document doc = documentBuilder.newDocument();
    marshaller.marshal(this.collaborations, doc);
    DOMSource domSource = new DOMSource(doc);
    StringWriter writer = new StringWriter();
    StreamResult result = new StreamResult(writer);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    transformer.transform(domSource, result);
    String xmlString = writer.toString();
    return xmlString;
  }

  /**
   * Returns the current Organizations instance as a String encoding of its XML representation.
   * 
   * @return Its XML String representation.
   * @throws Exception If problems occur during translation.
   */
  public String getOrganizationsXml() throws Exception {
    Marshaller marshaller = this.organizationsJaxbContext.createMarshaller();
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
    Document doc = documentBuilder.newDocument();
    marshaller.marshal(this.organizations, doc);
    DOMSource domSource = new DOMSource(doc);
    StringWriter writer = new StringWriter();
    StreamResult result = new StreamResult(writer);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    transformer.transform(domSource, result);
    String xmlString = writer.toString();
    return xmlString;
  }

  /**
   * Returns the current Researchers instance as a String encoding of its XML representation.
   * 
   * @return Its XML String representation.
   * @throws Exception If problems occur during translation.
   */
  public String getResearchersXml() throws Exception {
    Marshaller marshaller = this.researchersJaxbContext.createMarshaller();
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setNamespaceAware(true);
    DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
    Document doc = documentBuilder.newDocument();
    marshaller.marshal(this.researchers, doc);
    DOMSource domSource = new DOMSource(doc);
    StringWriter writer = new StringWriter();
    StreamResult result = new StreamResult(writer);
    TransformerFactory tf = TransformerFactory.newInstance();
    Transformer transformer = tf.newTransformer();
    transformer.transform(domSource, result);
    String xmlString = writer.toString();
    return xmlString;
  }
}
